/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.mojo.gwt;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collection;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;

import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.dev.GWTCompiler;
import com.google.gwt.dev.cfg.ModuleDef;
import com.google.gwt.dev.cfg.ModuleDefLoader;

/**
 * Goal which compiles a GWT file.
 * 
 * @goal compile
 * @requiresDependencyResolution compile
 * @phase compile
 */
public class CompileMojo
    extends AbstractMojo
{

  /**
   * Project classpath.
   * 
   * @parameter expression="${project.compileClasspathElements}"
   * @required
   * @readonly
   */
  private Collection<String> classpathElements;

  /**
   * Location of the source files.
   * 
   * @parameter expression="${basedir}/src/main/java"
   * @required
   */
  private File sourceDirectory;

  /**
   * Location of the file.
   * 
   * @parameter expression="${project.build.directory}/${project.artifactId}-${project.version}"
   * @required
   */
  private File outputDirectory;

  /**
   * The java class to compile.
   * 
   * @parameter
   * @required
   */
  private String[] classNames;

  /**
   * the class of the Gwt Compiler.
   */
  private static final String GWTCOMPILER_CLASS_NAME = "com.google.gwt.dev.GWTCompiler";



  /**
   * returns a sane {@link URL} for a {@link File}.
   * 
   * @param file
   *          the {@link File} to get the {@link URL} for
   * @return a sane {@link URL} for a {@link File}
   * @throws MalformedURLException
   *           if there is an error
   */
  URL getUrl(File file)
      throws MalformedURLException
  {
    return new URL(file.toURL().toString().replaceAll(" ", "%20"));
  }



  /**
   * compile the given class name with the given compiler.
   * 
   * @param gwtCompiler
   *          the GWT compiler
   * @param className
   *          the name of the class to compile
   * @throws UnableToCompleteException
   *           if an error occured
   */
  public void compile(GWTCompiler gwtCompiler, String className)
      throws UnableToCompleteException
  {
    gwtCompiler.setModuleName(className);
    gwtCompiler.setOutDir(outputDirectory);
    TreeLogger t = new MavenLogger(getLog());
    ModuleDef moduleDef = ModuleDefLoader.loadFromClassPath(t, gwtCompiler.getModuleName());

    gwtCompiler.distill(t, moduleDef);
  }



  /**
   * execute the plugin.
   * 
   * @throws MojoExecutionException
   *           when an error occured
   */
  public void execute()
      throws MojoExecutionException
  {

    if (getLog().isDebugEnabled()) {
      getLog().debug("CompileMojo#execute()");
    }

    try {
      addUrlToClassPath(getUrl(sourceDirectory));

      if (classpathElements != null) {
        for (String path : classpathElements) {
          try {
            addUrlToClassPath(getUrl(new File(path)));
          }
          catch (MalformedURLException e) {
            getLog().error(e);
          }
        }
      }
    }
    catch (Exception e) {
      throw new MojoExecutionException("Can't happen", e);
    }

    try {
      for (String className : classNames) {
        Class<?> compiler = null;
        try {
          compiler = getClass().getClassLoader().loadClass(GWTCOMPILER_CLASS_NAME);
        }
        catch (ClassNotFoundException e) {
          throw new MojoExecutionException("Could not find GWTCompiler.", e);
        }
        if (getLog().isDebugEnabled()) {
          getLog().debug("  Found class:" + compiler);
        }

        compile((GWTCompiler) compiler.newInstance(), className);
      }
    }
    catch (UnableToCompleteException e) {
      throw new MojoExecutionException("GWTCompiler#main(String[]) failed.", e);
    }
    catch (InstantiationException e) {
      throw new MojoExecutionException("Should not happen.", e);
    }
    catch (IllegalAccessException e) {
      throw new MojoExecutionException("Permission not granted for reflection.", e);
    }
  }

  /**
   * the arguments for retrieving the method of {@link URLClassLoader}.
   */
  private static final Class<?>[] PARAMETERS = new Class[] { URL.class };



  /**
   * hack method to add an {@link URL} to the current {@link URLClassLoader}.
   * 
   * @param url
   *          the {@link URL} to add
   * @throws IOException
   *           if an error occured
   */
  public static void addUrlToClassPath(URL url)
      throws IOException
  {
    URLClassLoader sysloader = (URLClassLoader) Thread.currentThread().getContextClassLoader();
    Class<URLClassLoader> sysclass = URLClassLoader.class;

    try {
      Method method = sysclass.getDeclaredMethod("addURL", PARAMETERS);
      method.setAccessible(true);
      method.invoke(sysloader, new Object[] { url });
    }
    catch (Throwable t) {
      throw new IOException("Error, could not add URL to system classloader");
    }
  }

}
